#include <linux/init.h>  /* module_init, ... */
#include <linux/module.h>  /* Needed by all modules */
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/netfilter/x_tables.h>
#include <linux/spinlock.h>

//debugging echo macros
#ifdef DEBUG_LAGZOR
	#define PRINTD(fmt, args...) printk(KERN_DEBUG "xt_lagzify: " fmt, ##args);
#else
	#define PRINTD(fmt, args...)
#endif

//forward declarations
/// Kernel module callback. Passes data to userspace, based on @attribute.
static ssize_t xt_lagzify_ko_show(struct kobject *showee_ko,
                                  struct attribute *attr, char *buffer);

/// Kernel module callback. Reads data from userspace, based on @attribute.
static ssize_t xt_lagzify_ko_store(struct kobject *storee_ko,
	                               struct attribute *attr, const char *buffer,
                                   size_t buffersize);

/// Kernel module callback. Called when kernel object dies.
static void xt_lagzify_ko_release(struct kobject *releasee_ko);

///Main xtables target function, packets go there.
static unsigned int lagzify(struct sk_buff *skb,
                            const struct xt_target_param *xt_targ);


static unsigned long packet_delay = 0, lagzified_packets = 0;
static struct kobject xt_lagzify_ko;
static struct sysfs_ops xt_lagzify_sysfs_ops = {
	.show  = xt_lagzify_ko_show,
	.store = xt_lagzify_ko_store
};

#define DECLARE_ATTRIBUTE(attname,__mode) \
static struct attribute attname##_attribute = { \
	.name = #attname, \
	.owner = THIS_MODULE, \
	.mode = __mode \
};

DECLARE_ATTRIBUTE(packet_delay,0666)
DECLARE_ATTRIBUTE(lagzified_packets,0444)
// all attributes shown in /sys/
static struct attribute *xt_lagzify_attributes[] = {
	&packet_delay_attribute,
	&lagzified_packets_attribute,
	NULL
};

static struct kobj_type xt_lagzify_kot = {
	.release = xt_lagzify_ko_release,
	.sysfs_ops = &xt_lagzify_sysfs_ops,
	.default_attrs = xt_lagzify_attributes
};

static struct xt_target lagzify_target = {
	.name       = "LAGZIFY",
	.target     = lagzify,
	.targetsize = XT_ALIGN(0),
	.table      = "filter",
	.me         = THIS_MODULE
};

static spinlock_t lagzify_lock = SPIN_LOCK_UNLOCKED;


static int __init xt_lagzify_init(void)
{
	int rv = 0;
	PRINTD("xt_lagzify initialization started...\n");

	rv = kobject_init_and_add(&xt_lagzify_ko, &xt_lagzify_kot,
	                          NULL,"xt_lagzify");
	if(rv){
		return rv;
	}
	kobject_uevent(&xt_lagzify_ko, KOBJ_ADD);// do i realy need this?

	rv = xt_register_target(&lagzify_target);
	if(rv){
		kobject_put(&xt_lagzify_ko); //@xt_lagzify_ko_release()
		                             //should be called as a result
		return rv;
	}
	PRINTD("xt_lagzify module initialized.\n");
	return 0;
}

static void __exit xt_lagzify_exit(void){
	PRINTD("xt_lagzify module unloading...\n");
	xt_unregister_target(&lagzify_target);
	kobject_put(&xt_lagzify_ko);
	PRINTD("xt_lagzify module unloaded.\n");
}

static unsigned int lagzify(struct sk_buff *skb,
                            const struct xt_target_param *xt_targ){
	//for now those packets just dropped.
	//TODO: add ACTUAL functionality

	//? does this whole function should be atomic?
	spin_lock(&lagzify_lock);
	++lagzified_packets;
	spin_unlock(&lagzify_lock);
	return NF_DROP;// drop it
	//TODO: NF_DROP -> NF_STEAL
}

static void xt_lagzify_ko_release(struct kobject *releasee_ko){

	//kfree(releasee_ko);
}

static ssize_t	xt_lagzify_ko_show(struct kobject *showee_ko,
                                   struct attribute *attr,char *buffer){
	if(&packet_delay_attribute == attr)
			return sprintf(buffer, "%ld\n", packet_delay);
	if(&lagzified_packets_attribute == attr)
			return sprintf(buffer, "%ld\n", lagzified_packets);
	return 0; // nothing written
}
static ssize_t	xt_lagzify_ko_store(struct kobject *storee_ko, 
                                    struct attribute *attr,const char *buffer,
                                    size_t buffersize){
	//FIXME: well, for now this is only one writable attribute. \
	//but it shure need to be checked.
	sscanf(buffer,"%ld\n",&packet_delay);
	return buffersize;
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ischuk Andrey <unlightable@gmail.com>");
MODULE_DESCRIPTION("Lagzification module. Adds some configurable delay \
to network packages.");
module_init(xt_lagzify_init);
module_exit(xt_lagzify_exit);
